/**
 * @author Alexander Farkas
 * v. 1.22
 */
(function ($)
{
    if (!document.defaultView || !document.defaultView.getComputedStyle)
    { // IE6-IE8
        var oldCurCSS = $.curCSS;
        $.curCSS = function (elem, name, force)
        {
            if (name === 'background-position')
            {
                name = 'backgroundPosition';
            }
            if (name !== 'backgroundPosition' || !elem.currentStyle || elem.currentStyle[name])
            {
                return oldCurCSS.apply(this, arguments);
            }
            var style = elem.style;
            if (!force && style && style[name])
            {
                return style[name];
            }
            return oldCurCSS(elem, 'backgroundPositionX', force) + ' ' + oldCurCSS(elem, 'backgroundPositionY', force);
        };
    }

    var oldAnim = $.fn.animate;
    $.fn.animate = function (prop)
    {
        if ('background-position' in prop)
        {
            prop.backgroundPosition = prop['background-position'];
            delete prop['background-position'];
        }
        if ('backgroundPosition' in prop)
        {
            prop.backgroundPosition = '(' + prop.backgroundPosition;
        }
        return oldAnim.apply(this, arguments);
    };

    function toArray(strg)
    {
        strg = strg.replace(/left|top/g, '0px');
        strg = strg.replace(/right|bottom/g, '100%');
        strg = strg.replace(/([0-9\.]+)(\s|\)|$)/g, "$1px$2");
        var res = strg.match(/(-?[0-9\.]+)(px|\%|em|pt)\s(-?[0-9\.]+)(px|\%|em|pt)/);
        return [parseFloat(res[1], 10), res[2], parseFloat(res[3], 10), res[3]];
    }

    $.fx.step.backgroundPosition = function (fx)
    {
        if (!fx.bgPosReady)
        {
            var start = $.css(fx.elem, 'backgroundPosition');
            if (!start)
            {//FF2 no inline-style fallback
                start = '0px 0px';
            }

            start = toArray(start);
            fx.start = [start[0], start[2]];
            var end = toArray(fx.end);
            fx.end = [end[0], end[2]];

            fx.unit = [end[1], end[3]];
            fx.bgPosReady = true;
        }
        //return;
        var nowPosX = [];
        nowPosX[0] = ((fx.end[0] - fx.start[0]) * fx.pos) + fx.start[0] + fx.unit[0];
        nowPosX[1] = ((fx.end[1] - fx.start[1]) * fx.pos) + fx.start[1] + fx.unit[1];
        fx.elem.style.backgroundPosition = nowPosX[0] + ' ' + nowPosX[1];

    };
})(jQuery);



/*
tlrkSlider

  example usage:

    $("#slider").tlrkSlider({
      autoStart: false,
      elements: {
        "img": {delay: 10},
        "h2": {delay: 500},
        ".copy": {delay: 800},
        ".button": {delay: 1000}
      }
    });

  to go to a specific frame:
    $("#slider").tlrkSlider("go", position);
    "position" can have one of the following values:
    "next", "prev", "first", "last", "+1", "-1" or a numeric value

  to start/stop the automatic loop:
    $("#slider").tlrkSlider("start");
    $("#slider").tlrkSlider("stop");

  to change the delay between automatic transitions:
    $("#slider").tlrkSlider("option", "delayAnimation", 1000);

  to change any option:
    $("#slider").tlrkSlider("option", option_name, option_value);

  Changing the "elements" object is not tested.

  Changing the following options: "navigation", "navigationClass", "framesSelector", "autoStart" won't have any effect for now.
  They are used only during the initialization.

  $("#slider").data("tlrkSlider") will return the plugin instance and the methods/properties can be accessed from there.

  The plugin contructor defaults are accessable through TlrkSlider.defaults

  The function that actually sweep the elements in/out can be overriden from
    TlrkSlider.prototype._animationIn and TlrkSlider.prototype._animationOut

    See sweepIn/sweepOut

 */
(function( $, window, document, undefined ){

    // utility function that generates the "dots" navigation
    function generateNavigation($el, count, config) {
        var i, html = "",
            width = count * 24;

        html += "<ol class='" + config.navigationClass + "' style='margin-left: -" + width/2 + "px; width: " + width + "px'>";
        for (i = 0; i < count; i++) {
            html += "<li><a " + (i === 0 ? "class='selected'" : "" ) + " href='#" + (i) + "'>slide</a></li>";
        }
        html += "</ol>";

        $el.append(html);
    }

    function sweepOut($el, windowWidth) {
        var dfr = $.Deferred(),
            pos = $el.position(),
            width = $el.width(),
            delta, final,
            options = $el.data("tlrkAnimOptions");

        windowWidth = windowWidth || $(window).width(); // check if the windowWidth is passed, if not - get it

        delta = windowWidth - pos.left;
        final = -(delta);

        setTimeout(function(){
            $el.animate({left: final, opacity: "toggle"}, options.speed, options.easing, function(){
                dfr.resolve();
            });
        }, options.delay);

        return dfr.promise();
    }

    function sweepIn($el, windowWidth, frameLeft) {
        var dfr = $.Deferred(),
            options = $el.data("tlrkAnimOptions"),
            positionData = $el.data("tlrkOriginalPos"),
            final = positionData.position.left,
            rightEdge;

        windowWidth = windowWidth || $(window).width(); // check if the windowWidth is passed, if not - get it

        $el.css({opacity: 0, display: "block"}); // move it outside the right edge of the screen
        $el.css("left", function(current){
            return current + windowWidth - frameLeft;
        });

        setTimeout(function(){
            $el.animate({left: final, opacity: 1}, options.speed, options.easing, function(){
                dfr.resolve();
            });
        }, options.delay);

        return dfr.promise();
    }


    // two pass function that first iterates all the elements and gets their position/width/height
    // and then sets their position to absolute
    function absolutize($elements) {

        // todo - move it to separate function and do it just once
        // gather the original position/dimension data for all elements
        $elements.each(function(){
            var $t = $(this);

            if ($t.data("tlrkOriginalPos")) return

            $t.data("tlrkOriginalPos", {
                position: $t.position(),
                width: $t.width(),
                height: $t.height(),
                css_pos: $t.css("position"),
                css_left: $t.css("left"),
                css_top: $t.css("top"),
                css_width: $t.css("width") || "auto",
                css_height: $t.css("height") || "auto"
            });

        });

        // set the absolute position
        $elements.each(function(){
            var $t = $(this),
                opos = $t.data("tlrkOriginalPos");

            $t.css({
                position: "absolute",
                left: opos.position.left,
                top: opos.position.top,
                width: opos.width,
                height: opos.height
            });
        });
    }

    function restoreFrameElements($elements) {
        $elements.each(function(){
            var $t = $(this),
                opos = $t.data("tlrkOriginalPos");

            if (!opos) return

            $t.css({
                position: opos.css_pos,
                left: opos.css_left,
                top: opos.css_top,
                width: opos.css_width,
                height: opos.css_height
            });
        });

    }

    var TlrkSlider = function( elem, options ){
        this.elem = elem;
        this.$elem = $(elem);
        this.options = options;
    };

    // the plugin prototype
    TlrkSlider.prototype = {
        defaults: {


            defaultElementOptions: {
                speed: 1200,
                easing: "easeInOutBack",
                // interval before the element starts moving when the fadeIn/Out functions are called
                // it's a good idea to give different delays for the different elements
                // if all have the same delay they'll start moving all together
                delay: 100
            },

            // dispose elements are these that are not included in the elements object
            // but affect the document flow and will be fadedIn/Out
            disposeDelay: 100, // delay for the dispose elements
            disposeSpeed: 1000, // how quickly they'll fadeOut/In

            delayBetweenTransition: 1000, // time between starting fadeOut and fadeIn
            delayAnimation: 7000, // time between auto changing the current frame

            loop: true, // if true when clicking next on the last frame the slider jumps to the first one

            autoStart: true, // start the automatic looping through the frames on init

            framesSelector: "section", // selector for the frames inside the slider

            elements: {
                "p": {delay: 100, speed: 1000, easing: "easeInOutBack"}
            },

            navigation: true, // the dots navigation on the bottom
            navigationClass: "slider-nav",

            // callbacks
            // another way to "catch" these events is with
            // $(-slider-element-).bind("animationStart")
            animationStart: null,
            animationEnd: null
        },

        init: function() {
            var c, e, element, $element,
                that = this,
                $firstFrame;

            c = this.config = $.extend({}, this.defaults, this.options);

            this.elem.style.position = "relative"; // make the wrapping element relative

            // basics
            this.$frames = this.$elem.find(c.framesSelector);
            this.framesCount = this.$frames.length;
            this.currentFrame = 0;
            this.queue = [];

            this._$elementsByFrame = {};
            this._$disposeElementsByFrame = {};

            for (i = 0; i < this.framesCount; i++) {
                this._$elementsByFrame[i] = this._getFrameElements(i); // cache the $elements by frame
                this._$disposeElementsByFrame[i] = this._getDisposeFrameElements(i); // cache the rest of the tree for each frame
            }

            if (c.navigation) {
                generateNavigation(this.$elem, this.framesCount, c);
                this.$navigation = this.$elem.find("."+c.navigationClass);
            }

            // bindings
            this.$elem.find(".slider-nav").delegate("a", "click", function(e){
                var frame = this.getAttribute("href").split("#")[1];
                that.go.call(that, frame);
                return false;
            });

            this.$elem // internal bindings for the callbacks
                .bind("animationStart", function(){
                    if ($.isFunction(c.animationStart)) {c.animationStart.apply(that, arguments);}
                })
                .bind("animationEnd", function(){
                    if ($.isFunction(c.animationEnd)) {c.animationEnd.apply(that, arguments);}
                })
            ;

            // start animation?
            if (c.autoStart) {
                this.start();
            } else {
                this.running = false;
            }

            return this;
        },

        start: function(instant) {
            var that = this;

            if (this.timer) { // we'll clear the current timer
                window.clearTimeout(this.timer);
            }

            this.running = true;

            if (instant) {
                that.nextFrame();
            } else {
                this.timer = window.setTimeout(function(){ that.nextFrame.call(that) }, that.config.delayAnimation);
            }
        },

        stop: function() {
            if (!this.running) return; // we are not running

            this.running = false;
            window.clearTimeout(this.timer);
        },

        // main function for changing frames
        selectFrame: function(frame, dfr) {
            var c = this.config, // shorthand for the config
                that = this,
                dfr = dfr || $.Deferred(),
                dFadeIn = $.Deferred(),
                dFadeOut = $.Deferred();

            if (isNaN(frame) || frame < 0 || frame > this.framesCount || frame === this.currentFrame) {
                dfr.reject();
                return dfr.promise();
            }

            // clear the animation loop interval if the animation is running
            if (this.running && this.timer) {
                window.clearTimeout(this.timer);
            }

            // check if we are currenly running an animation.
            if (this.animated && this.queue.length > 0) {
                // wait for the last item in the queue to finish
                this.queue[this.queue.length-1].done(function(){
                    that.selectFrame(frame, dfr); // and call again the selectFrame
                })
                return dfr.promise();
            }

            this.animated = true;
            this.$elem.trigger("animationStart", [this, frame]);

            this.queue.push(dfr);

            // fade the frames
            dFadeOut = this._fadeOutFrame(this.currentFrame);

            // hide the fadetout frame
            dFadeOut.done(function(){
                that.$frames.eq(that.currentFrame).hide();
            });

            window.setTimeout(function(){ // then wait delayBetweenTransition and fadeIn the new frame
                dFadeIn = that._fadeInFrame.call(that, frame).done(function(){
                    // when both the fadeIn and fadeOut are done we'll resolve the selectFrame promise
                    $.when(dFadeOut, dFadeIn).done(function(){
                        that.animated = false;
                        that.queue.shift();
                        that.$elem.trigger("animationEnd", [that]);
                        that.currentFrame = frame;
                        dfr.resolve();
                    });
                });
            }, c.delayBetweenTransition);

            // navigation html change
            if (this.config.navigation) {
                this.$navigation.find(".selected").removeClass("selected").end()
                    .find("a").eq(frame).addClass("selected");
            }

            dfr.done(function(){ // we'll resume the loop animation after the transitions are done
                if (that.running) {
                    that.start();
                }
            });

            return dfr.promise();
        },

        _fadeFrame: function(frame, callback, direction) {
            var dfr = $.Deferred(),
                $frame = this.$frames.eq(frame),
                $elements = this._$elementsByFrame[frame],
                windowWidth = $(window).width(), // cache it before the animations, so we don't have to check it for each element
                i, len,
                that = this,
                elementAnimations = [],
                $disposeElements = this._$disposeElementsByFrame[frame],
                $affectedElements,
                frameLeft = $frame.offset().left;

            direction = direction || "out";

            if (!$.isFunction(callback)) return; // do nothing if there's no callback passed

            $affectedElements = $elements.add($disposeElements);

            // position absolute the animation and dispose elements
            absolutize($affectedElements);

            // toggle the dispose elements
            if ($disposeElements.length > 0) {
                window.setTimeout(function(){
                    $disposeElements[direction === "out" ? "fadeOut" : "fadeIn"](that.config.disposeSpeed);
                }, this.config.disposeDelay);
            }

            // invoke the callback for each element
            // the callback must return a promise
            $elements.each(function(){
                elementAnimations.push( callback.call(that, $(this), windowWidth, frameLeft) );
            });

            // wait for all the elements to finish their animation
            $.when.apply(this, elementAnimations).done(function(){
                //restoreFrameElements($affectedElements); // and restore the elements' position
                dfr.resolve(); // resolve the fade function
            });

            return dfr.promise();
        },

        _fadeOutFrame: function(frame) {
            var dfr = $.Deferred(),
                $frame = this.$frames.eq(frame),
                $disposeElements = this._$disposeElementsByFrame[frame];

            this._fadeFrame(frame, this._animationOut, "out").done(function(){
                dfr.resolve();
            })

            return dfr.promise();
        },

        _fadeInFrame: function(frame) {
            var dfr = $.Deferred(),
                $frame = this.$frames.eq(frame),
                $elements = this._$elementsByFrame[frame];

            this._restoreFrame(frame);

            $frame.show();

            this._fadeFrame(frame, this._animationIn, "in").done(function(){
                dfr.resolve();
            });

            return dfr.promise();
        },

        _restoreFrame: function(frame){
            if (!frame) return
            restoreFrameElements( this._$elementsByFrame[frame].add(this._$disposeElementsByFrame[frame]) );
        },

        nextFrame: function() {
            var frame = this.currentFrame+1,
                dfr = $.Deferred();

            if (frame > this.framesCount-1) {
                if (this.config.loop) {
                    frame = 0;
                } else {
                    dfr.reject();
                }
            };

            this.selectFrame(frame).done(function(){
                dfr.resolve();
            });

            return dfr.promise();
        },

        prevFrame: function() {
            var frame = this.currentFrame-1,
                dfr = $.Deferred();

            if (frame < 0) {
                if (this.config.loop) {
                    frame = this.framesCount-1;
                } else {
                    dfr.reject();
                    return dfr.promise();
                }
            }

            this.selectFrame(frame).done(function(){
                dfr.resolve();
            });

            return dfr.promise();
        },

        go: function(str) { // shorthand
            switch (str) {
                case "next":
                case "+1":
                    this.nextFrame();
                    break;

                case "prev":
                case "-1":
                    this.prevFrame();
                    break;

                case "first":
                    this.selectFrame(0);
                    break;

                case "last":
                    this.selectFrame(this.framesCount-1);
                    break;

                default:
                    if (isNaN(str)) return;
                    this.selectFrame(Number(str));
            }
        },

        // returns jquery collection of animation elements
        _getFrameElements: function(frame) {
            var $frame = this.$frames.eq(frame),
                elements = this.config.elements,
                e, elementOptions,
                $found, $frameElements = $([]);

            for (e in elements) {
                elementOptions = elements[e];
                $found = $frame.find(e);
                $found.addClass("t-frame-element").data("tlrkAnimOptions", $.extend({}, this.defaults.defaultElementOptions, elementOptions ));
                $frameElements = $frameElements.add($found);
            }

            return $frameElements;
        },

        // returns jquery collection of elements that have to be faded out
        // i.e. elements on the same level as the animation elements
        // that doesn't contain other animation elements
        _getDisposeFrameElements: function(frame) {
            var $disposeElements = $([]),
                $frame = this.$frames.eq(frame),
                $elements = this._$elementsByFrame[frame];

            $elements.each(function(){
                var $t = $(this),
                    $siblings = $t.siblings().not(".t-frame-element");

                $siblings.each(function(){
                    var $t = $(this);
                    // check if the node is not already marked and doesn't contains other frame elements
                    if (!$t.hasClass("t-frame-dispose") && $t.find(".t-frame-element").length === 0) {
                        $t.addClass("t-frame-dispose");
                        $disposeElements = $disposeElements.add($t);
                    }
                });

            });
            return $disposeElements;
        },


        // expose the internal animationIn/Out functions that are called for each element in the frame
        // two arguments are passed - the $element which have to be animated and the window width
        _animationIn: sweepIn,
        _animationOut: sweepOut

    }

    TlrkSlider.defaults = TlrkSlider.prototype.defaults;

    $.fn.tlrkSlider = function(options) {
        var otherArgs = Array.prototype.slice.call(arguments, 1);

        return this.each(function() {
            var $el = $(this),
                pluginData = $el.data("tlrkSlider");

            if (!pluginData) { // check if the slider is already attached
                pluginData = new TlrkSlider(this, options).init();
                $el.data("tlrkSlider", pluginData);
                return;
            }

            //change the options or call a method
            if (typeof options === "string") {

                // setting / getting option(s)
                if (options === "option") {

                    if (typeof otherArgs[0] === "string" && typeof otherArgs[1] !== "undefined") { // set an option value
                        pluginData.config[otherArgs[0]] = otherArgs[1];
                    }

                    if (typeof otherArgs[0] === "object") { // extend the config with new options
                        pluginData.config = $.extend(pluginData.config, otherArgs[0]);
                    }

                } else { // call a method?
                    try {
                        pluginData[options].apply(pluginData, otherArgs);
                    } catch(ex) {
                        throw "Error calling a plugin method (" + ex + ")";
                    }
                }
            }
        });
    };

    window.TlrkSlider = TlrkSlider;

})( jQuery, window , document );
